﻿/*
   Copyright 2012 University of Southampton

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml.Serialization;
using System.Data.Linq;
using System.Xml;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using uk.ac.soton.ses.DAL;
using System.Diagnostics;
using System.Data.EntityClient;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Data.SqlTypes;

namespace uk.ac.soton.ses
{

    public class ExperimentMetadataContainerFileSystem : ExperimentMetadataContainer
    {
        private HDCPath _path = null;
        private readonly string[] folderNamesToIgnore = HDCConfiguration.Instance.ConfigurationOptions.FileSystemWatcherOptions.FolderNamesToIgnore;
        private readonly string[] fileNamesToIgnore = HDCConfiguration.Instance.ConfigurationOptions.FileSystemWatcherOptions.FileNamesToIgnore;
        private FileSystemRights ourReadRights = FileSystemRights.ReadAndExecute | FileSystemRights.Synchronize;
        private FileSystemRights ourWriteRights = FileSystemRights.Modify | FileSystemRights.Synchronize;

        public ExperimentMetadataContainerFileSystem(HDCPath experimentPath):base()
        {
            this._path = experimentPath;

            string experimentFullPath = experimentPath.BasePath;
            if (!Directory.Exists(experimentFullPath))
            {
                throw new DirectoryNotFoundException(string.Format("Experiment {0} not found", experimentFullPath));
            }

            DirectoryInfo experimentFullPathInfo = new DirectoryInfo(experimentFullPath);
            DirectorySecurity directorySecurity = experimentFullPathInfo.GetAccessControl();
            AuthorizationRuleCollection rules = directorySecurity.GetAccessRules(true, false, typeof(SecurityIdentifier));
            
            base._experiment = new ExperimentBO
            {
                Name = experimentFullPathInfo.Name,
                DefaultBasePath = this._path.BasePath,
                //DefaultBasePath = experimentFullPathInfo.FullName,
                Date = experimentFullPathInfo.LastWriteTime,
                //OwnerID = 0,
                FileSystemUserID = directorySecurity.GetOwner(typeof(SecurityIdentifier)).ToString(),
                ReadUsers = rules.Cast<FileSystemAccessRule>().Where(rule=>(rule.FileSystemRights ^ ourReadRights)==0).Select(rule=>rule.IdentityReference.Value).ToArray(),
                WriteUsers = rules.Cast<FileSystemAccessRule>().Where(rule=>(rule.FileSystemRights ^ ourWriteRights)==0).Select(rule=>rule.IdentityReference.Value).ToArray()
            };

            base._experimentDataFiles = new List<ExperimentDataFileMetadataContainer>();
            foreach (FileInfo file in experimentFullPathInfo.GetFiles("*", SearchOption.AllDirectories))
            {
                if (fileNamesToIgnore.Contains(file.Name, StringComparer.OrdinalIgnoreCase))
                {
                    string message = string.Format("{0} is a file we ignore", file.Name);
                    HDCFileSystemWatcher.Message(message, TraceLevel.Info);
                }
                else if(folderNamesToIgnore.Contains(file.Directory.Name, StringComparer.OrdinalIgnoreCase ))
                {
                    string message = string.Format("{0} is a folder we ignore", file.Directory.Name);
                    HDCFileSystemWatcher.Message(message, TraceLevel.Verbose);
                }
                else
                {
                    ExperimentDataFileBO datafile = this.GetBODataFromFileInfo(file);
                    base._experimentDataFiles.Add(new ExperimentDataFileMetadataContainerFileSystem(datafile, this));

                    //Commented as only needed for debugging
                    //HDCFileSystemWatcher.Message(string.Format("{0} file owner: {1}", datafile.FullPath, datafile.FileSystemUserID), ConsoleColor.Cyan);
                }
            }
        }

        public ExperimentMetadataContainerFileSystem(HDCPath experimentPath, ExperimentMetadataContainerDB dbMeta)
            : base()
        {
            this._path = experimentPath;
            string experimentFullPath = experimentPath.BasePath;

            if (!Directory.Exists(experimentFullPath))
            {
                throw new DirectoryNotFoundException(string.Format("Experiment {0} not found", experimentFullPath));
            }

            DirectoryInfo experimentFullPathInfo = new DirectoryInfo(experimentFullPath);
            DirectorySecurity directorySecurity = experimentFullPathInfo.GetAccessControl();
            AuthorizationRuleCollection rules = directorySecurity.GetAccessRules(true, false, typeof(SecurityIdentifier));

            base._experiment = new ExperimentBO
            {
                Name = experimentFullPathInfo.Name,
                DefaultBasePath = this._path.BasePath,
                //DefaultBasePath = experimentFullPathInfo.FullName,
                Date = experimentFullPathInfo.LastWriteTime,
                //OwnerID = 0,
                FileSystemUserID = directorySecurity.GetOwner(typeof(SecurityIdentifier)).ToString(),
                ReadUsers = rules.Cast<FileSystemAccessRule>().Where(rule => (rule.FileSystemRights ^ ourReadRights) == 0).Select(rule => rule.IdentityReference.Value).ToArray(),
                WriteUsers = rules.Cast<FileSystemAccessRule>().Where(rule=>(rule.FileSystemRights ^ ourWriteRights)==0).Select(rule=>rule.IdentityReference.Value).ToArray()
            };

            base._experimentDataFiles = new List<ExperimentDataFileMetadataContainer>();
            foreach (FileInfo file in experimentFullPathInfo.GetFiles("*", SearchOption.AllDirectories))
            {
                if (fileNamesToIgnore.Contains(file.Name, StringComparer.OrdinalIgnoreCase))
                {
                    string message = string.Format("{0} is a file we ignore", file.Name);
                    HDCFileSystemWatcher.Message(message, TraceLevel.Info);
                }
                else if(folderNamesToIgnore.Contains(file.Directory.Name, StringComparer.OrdinalIgnoreCase ))
                {
                    string message = string.Format("{0} is a folder we ignore", file.Directory.Name);
                    HDCFileSystemWatcher.Message(message, TraceLevel.Verbose);
                }
                else
                {
                    ExperimentDataFileBO datafile = this.GetBODataFromDBInfo(file, dbMeta);
                    base._experimentDataFiles.Add(new ExperimentDataFileMetadataContainerFileSystem(datafile, this));

                    //Commented as only needed for debugging
                    //HDCFileSystemWatcher.Message(string.Format("{0} file owner: {1}", datafile.FullPath, datafile.FileSystemUserID), ConsoleColor.Cyan);
                }
            }

        }

        public ExperimentDataFileBO GetBODataFromFile(string filePath)
        {
            FileInfo fileInfo = new FileInfo(filePath);
            return this.GetBODataFromFileInfo(fileInfo);
        }

        public ExperimentDataFileBO GetBODataFromFileInfo(FileInfo fileInfo)
        {
            HDCPath hdcpathinfo = new HDCPath(fileInfo.FullName, this._path.WatchFolder, this._path.BasePathDepthInWatchFolder);
            FileSecurity fileSecurity = fileInfo.GetAccessControl();

            //Get SHA1 Hash which might take some time.
            string sha1Hash = Checksum.GetSHA1Sum(fileInfo.FullName);
            DateTime sha1HashGeneratedTime = DateTime.Now;

            //Once we have it, then generate a new FileInfo in case the FileInfo we are using is out of date
            FileInfo newFileInfo = new FileInfo(fileInfo.FullName);

            ExperimentDataFileBO datafile = new ExperimentDataFileBO
            {
                ID = 0,
                RemainingPath = hdcpathinfo.RemainingPath,
                LastWriteTime = newFileInfo.LastWriteTime,
                LastAccessTime = newFileInfo.LastAccessTime,
                CreationTime = newFileInfo.CreationTime,
                BasePath = hdcpathinfo.BasePath,
                IsDeleted = false,
                FileSize = newFileInfo.Length,
                SHA1Hash = sha1Hash,
                SHA1HashGeneratedTime = sha1HashGeneratedTime,
                FileSystemUserID = fileSecurity.GetOwner(typeof(SecurityIdentifier)).ToString(),
                ExperimentDatatypeID = 0
            };
            return datafile;
        }

        public ExperimentDataFileBO GetBODataFromDBInfo(FileInfo fileInfo, ExperimentMetadataContainerDB dbMeta)
        {
            HDCPath hdcpathinfo = new HDCPath(fileInfo.FullName, this._path.WatchFolder, this._path.BasePathDepthInWatchFolder);
            IEnumerable<ExperimentDataFileMetadataContainer> dbDataFiles =
                dbMeta.ExperimentDataFiles
                .Where(df =>
                       df.ExperimentDataFile.BasePath.Equals(hdcpathinfo.BasePath,StringComparison.OrdinalIgnoreCase)
                    && df.ExperimentDataFile.RemainingPath.Equals(hdcpathinfo.RemainingPath,StringComparison.OrdinalIgnoreCase)
                    && ExperimentMetadataContainerComparer.TimesEqual(df.ExperimentDataFile.LastWriteTime, fileInfo.LastWriteTime)
                    && ExperimentMetadataContainerComparer.TimesEqual(df.ExperimentDataFile.CreationTime, fileInfo.CreationTime)
                    && ExperimentMetadataContainerComparer.TimeIsLater(fileInfo.LastWriteTime, df.ExperimentDataFile.SHA1HashGeneratedTime)
                    && df.ExperimentDataFile.FileSize == fileInfo.Length
                    && df.ExperimentDataFile.IsDeleted == false);

            ExperimentDataFileMetadataContainer dbDataFile = dbDataFiles.FirstOrDefault();
            if (dbDataFile == null)
            {
                string message = string.Format("Hash not found in database for file {0}", hdcpathinfo.RemainingPath);
                HDCFileSystemWatcher.Message(message, TraceLevel.Info);

                return this.GetBODataFromFileInfo(fileInfo);
            }
            else
            {
                FileSecurity fileSecurity = fileInfo.GetAccessControl();
                ExperimentDataFileBO datafile = new ExperimentDataFileBO()
                {
                    ID = 0,
                    RemainingPath = hdcpathinfo.RemainingPath,
                    LastWriteTime = fileInfo.LastWriteTime,
                    LastAccessTime = fileInfo.LastAccessTime,
                    CreationTime = fileInfo.CreationTime,
                    BasePath = hdcpathinfo.BasePath,
                    IsDeleted = false,
                    FileSize = fileInfo.Length,
                    SHA1Hash = dbDataFile.ExperimentDataFile.SHA1Hash,
                    SHA1HashGeneratedTime = dbDataFile.ExperimentDataFile.SHA1HashGeneratedTime,
                    FileSystemUserID = fileSecurity.GetOwner(typeof(SecurityIdentifier)).ToString(),
                    ExperimentDatatypeID = 0
                };
                return datafile;
            }
        }

        public ExperimentMetadataContainerDB SaveToDB()
        {
            //Create the database metadata from the file data
            ExperimentMetadataContainerDB dbmeta = ExperimentMetadataContainerDB.Create(this);

            //Make sure we return the metadata
            return dbmeta;
        }

        public static ExperimentMetadataContainerFileSystem Create(HDCPath experimentFullPath)
        {
            if (Directory.Exists(experimentFullPath.BasePath))
            {
                throw new ExperimentMetadataContainerException(string.Format("Directory {0} already exists.", experimentFullPath));
            }
            Directory.CreateDirectory(experimentFullPath.BasePath);
            return new ExperimentMetadataContainerFileSystem(experimentFullPath);
        }

        public override ExperimentDataFileMetadataContainer AddExperimentDataFile(ExperimentDataFileBO experimentDataFile)
        {
            //We can only create a blank file, it's debatable whether we should support this
            string filePath = experimentDataFile.GetFullPath();
            if (!File.Exists(filePath))
            {
                //Create the new file
                using (FileStream newFile = File.Create(experimentDataFile.GetFullPath()))
                {
                }

                //this.SetFileData(filePath, experimentDataFile);
                ExperimentDataFileBO newDataFile = this.GetBODataFromFile(filePath);
                ExperimentDataFileMetadataContainerFileSystem newDataFileContainer = new ExperimentDataFileMetadataContainerFileSystem(newDataFile, this);
                newDataFileContainer.SetFileData(experimentDataFile);
                base._experimentDataFiles.Add(newDataFileContainer);
                return newDataFileContainer;
            }
            else
            {
                throw new IOException(string.Format("{0} already exists", filePath));
            }
        }

    }

}
